/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Data;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  internal class ItemsWrapper : IList, INotifyCollectionChanged, INotifyPropertyChanged
  {
    #region Constructors

    public ItemsWrapper( ListBox listBox )
    {
      if( listBox == null )
        throw new ArgumentNullException( "listBox" );

      m_listBox = listBox;
    }

    #endregion

    #region Internal Properties

    public CollectionViewSource CollectionViewSource
    {
      get
      {
        if( m_listBox.IsAsyncDataSource )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

        if( m_cachedCollectionViewSource == null )
        {
          m_cachedCollectionViewSource = new CollectionViewSource();

          using( m_cachedCollectionViewSource.DeferRefresh() )
          {
            // Do not access the DataSourceWrapper if the ItemsSource is null
            // to avoid reentrancy caused by ListBox.UpdateDataSourceAndDataSourceEnumerator()
            // when initializing the DataSourceEnumeratorWrapper for an Unbound ListBox.
            if( m_listBox.ItemsSource != null )
            {
              DataSourceWrapper dataSourceWrapper = m_listBox.DataSourceWrapper;

              if( dataSourceWrapper != null )
              {
                ListDataSourceProvider listDataSourceProvider = dataSourceWrapper.Provider as ListDataSourceProvider;

                m_cachedCollectionViewSource.Source = ( listDataSourceProvider == null ) ? null : listDataSourceProvider.DataSource;

                foreach( SortDescription sortDescription in dataSourceWrapper.SortDescriptions )
                {
                  m_cachedCollectionViewSource.SortDescriptions.Add( sortDescription );
                }
              }
            }
            else
            {
              // The ListBox is treated as Unbound
              Debug.Assert( m_listBox.ItemsSource == null, "Using UnboundItems on a ListBox that is not Unbound." );

              m_cachedCollectionViewSource.Source = this.UnboundItems;
            }

            // Apply GroupDescriptions defined on the ListBox
            foreach( GroupDescription groupDescription in m_listBox.GroupDescriptions )
            {
              m_cachedCollectionViewSource.GroupDescriptions.Add( groupDescription );
            }
          }
        }

        Debug.Assert( m_cachedCollectionViewSource != null );

        return m_cachedCollectionViewSource;
      }
    }

    public ObservableCollection<object> UnboundItems
    {
      get
      {
        if( m_unboundList == null )
        {
          m_unboundList = new ObservableCollection<object>();
          m_unboundList.CollectionChanged += this.OnUnboundList_CollectionChanged;
        }

        return m_unboundList;
      }
    }

    private void OnUnboundList_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( !m_listBox.IsInitialized )
        return;

      this.RaiseNotifyPropertyChanged( "Count" );
      this.RaiseNotifyCollectionChanged( e );
    }

    #endregion

    #region Internal Methods

    internal void UpdateItemsSource()
    {
      // Will be re-initialized if required when going through the CollectionViewSource property getter.
      m_cachedCollectionViewSource = null;

      this.RaiseNotifyPropertyChanged( "Count" );
      this.RaiseNotifyCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ) );
    }

    #endregion

    #region Private Fields

    private ObservableCollection<object> m_unboundList;
    private ListBox m_listBox;
    private CollectionViewSource m_cachedCollectionViewSource;

    #endregion

    #region IList Members

    public int Add( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return ( ( IList )this.UnboundItems ).Add( value );
    }

    public void Clear()
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Clear();
    }

    public bool Contains( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return this.UnboundItems.Contains( value );
    }

    public int IndexOf( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return this.UnboundItems.IndexOf( value );
    }

    public void Insert( int index, object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Insert( index, value );
    }

    public bool IsFixedSize
    {
      get
      {
        return ( m_listBox.ItemsSource != null );
      }
    }

    public bool IsReadOnly
    {
      get
      {
        return ( m_listBox.ItemsSource != null );
      }
    }

    public void Remove( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Remove( value );
    }

    public void RemoveAt( int index )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.RemoveAt( index );
    }

    public object this[ int index ]
    {
      get
      {
        if( m_listBox.IsAsyncDataSource )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

        if( m_listBox.ItemsSource == null )
        {
          return this.UnboundItems[ index ];
        }
        else
        {
          if( m_listBox.IsAsyncDataSource )
            throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

          DataPath returnedDataPath = null;

          ICollection<DataPath> fetchedItems;

          DataSourceEnumeratorWrapper enumerator = m_listBox.DataSourceEnumeratorWrapper;
          DataPath oldCurrent = enumerator.Current;

          enumerator.MoveToHome();

          if( enumerator.MoveAndFetch( index, 1, out fetchedItems ) )
          {
            returnedDataPath = fetchedItems.First<DataPath>();
            enumerator.JumpMoveAndFetch( oldCurrent, 0, 1, out fetchedItems );
          }

          return ( returnedDataPath != null )
            ? returnedDataPath.LastChild.Data
            : null;
        }
      }
      set
      {
        if( m_listBox.ItemsSource != null )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

        this.UnboundItems[ index ] = value;
      }
    }

    #endregion

    #region ICollection Members

    public void CopyTo( Array array, int index )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      ( ( ICollection )this.UnboundItems ).CopyTo( array, index );
    }

    public int Count
    {
      get
      {
        if( m_listBox.ItemsSource != null )
          return ( int )m_listBox.DataSourceEnumeratorWrapper.ItemCount;

        return this.UnboundItems.Count;
      }
    }

    public bool IsSynchronized
    {
      get
      {
        return false;
      }
    }

    public object SyncRoot
    {
      get
      {
        return this;
      }
    }

    #endregion

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
      return this.CollectionViewSource.View.GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void RaiseNotifyCollectionChanged( NotifyCollectionChangedEventArgs args )
    {
      if( args == null )
        throw new ArgumentNullException( "args" );

      if( this.CollectionChanged == null )
        return;

      this.CollectionChanged( this, args );
    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaiseNotifyPropertyChanged( string propertyName )
    {
      if( this.PropertyChanged == null )
        return;

      if( propertyName == null )
        propertyName = string.Empty;

      this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion
  }
}
